using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.ComponentModel;


namespace ScintillaNet
{
	[TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
	public class SnippetManager : ScintillaHelperBase
	{
		
		#region Private Member variables
		private SnippetChooser _snipperChooser;
		private Timer _snippetLinkTimer = new Timer();
		private bool _pendingUndo = false;
		private readonly Regex snippetRegex1 = new Regex(@"(?<dm>\\\x00abDropMarker(?<dmi>\\[[0-9]*\\])?\\\x00ab)|(?<c>\\\x00abcaret\\\x00ab)|(?<a>\\\x00abanchor\\\x00ab)|(?<e>\\\x00abend\\\x00ab)|(?<s>\\\x00abselected\\\x00ab)|(?<l>\\\x00ab.+?(?<li>\\[[0-9]*\\])?\\\x00ab)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
		private SnippetLinkCollection _snippetLinks = new SnippetLinkCollection();

		#endregion

		public SnippetManager(Scintilla scintilla)
			: base(scintilla)
		{
			_list = new SnippetList(this);

			_snippetLinkTimer.Interval = 1;
			_snippetLinkTimer.Tick += new EventHandler(_snippetLinkTimer_Tick);

			IsEnabled = _isEnabled;
		}

		internal void SetIndicators()
		{
			Scintilla.Indicators[_activeSnippetIndicator].Style = _activeSnippetIndicatorStyle;
			Scintilla.Indicators[_activeSnippetIndicator].Color = _activeSnippetColor;

			Scintilla.Indicators[_inactiveSnippetIndicator].Style = _inactiveSnippetIndicatorStyle;
			Scintilla.Indicators[_inactiveSnippetIndicator].Color = _inactiveSnippetColor;
		}

		internal bool ShouldSerialize()
		{
			return ShouldSerializeActiveSnippetColor() ||
				ShouldSerializeActiveSnippetIndicator() ||
				ShouldSerializeActiveSnippetIndicatorStyle() ||
				ShouldSerializeInactiveSnippetColor() ||
				ShouldSerializeInactiveSnippetIndicator() ||
				ShouldSerializeInactiveSnippetIndicatorStyle()||
				ShouldSerializeIsOneKeySelectionEmbedEnabled() ||
				ShouldSerializeIsOneKeySelectionEmbedEnabled() ||
				ShouldSerializeDefaultDelimeter();
		}

		#region Public Methods
		public bool NextSnippetRange()
		{
			//	This would be a whole lot easier if I had the Command Contexts set
			//	up. The way it's working now is that this command will always execute
			//	irregardlessly of if the SnippetLinks are active. Since we may not have
			//	a valid context to execute we don't necessarily want to eat the
			//	keystroke in all circumstances, hence the bool return
			if (!_snippetLinks.IsActive || Scintilla.AutoComplete.IsActive)
				return false;

			//	OK So we want to find the next SnippetLink in
			//	whatever order they are in and then select it
			//	so that they can fill it out.
			SnippetLink sl = _snippetLinks.NextActiveSnippetLink;
			if (sl != null)
			{
				//	However it is possible that all of this Snippet Links'
				//	ranges have been deleted by the user. If this is the case
				//	we need to remove this snippet link from the list and go
				//	to the next link.

				while (sl.Ranges.Count == 0)
				{
					_snippetLinks.Remove(sl);
					sl = _snippetLinks.NextActiveSnippetLink;

					//	No more snippet links? Nothing to do but quit
					if (sl == null)
					{
						Scintilla.Commands.StopProcessingCommands = true;
						return true;
					}
				}

				//	Yay we have it. Select the first Range in the Snippet Link's Series
				sl.Ranges[0].Select();
				Scintilla.Commands.StopProcessingCommands = true;
				return true;
			}

			return false;
		}

		public bool PreviousSnippetRange()
		{
			//	Same as NextSnippetRange but going in the opposite direction
			if (!_snippetLinks.IsActive || Scintilla.AutoComplete.IsActive)
				return false;

			SnippetLink sl = _snippetLinks.PreviousActiveSnippetLink;
			if (sl != null)
			{
				while (sl.Ranges.Count == 0)
				{
					_snippetLinks.Remove(sl);
					sl = _snippetLinks.PreviousActiveSnippetLink;
					if (sl == null)
					{
						Scintilla.Commands.StopProcessingCommands = true;
						return true;
					}
				}

				sl.Ranges[0].Select();
				Scintilla.Commands.StopProcessingCommands = true;
				return true;
			}

			return false;
		}

		public bool CancelActiveSnippets()
		{
			if (_snippetLinks.IsActive && !Scintilla.AutoComplete.IsActive)
			{
				IsActive = false;
				Scintilla.Commands.StopProcessingCommands = true;
				return true;
			}

			return false;
		}

		public bool AcceptActiveSnippets()
		{
			if (_snippetLinks.IsActive && !Scintilla.AutoComplete.IsActive)
			{
				int pos = Scintilla.Caret.Position;
				bool end = false;
				foreach (SnippetLink sl in _snippetLinks.Values)
				{
					foreach (SnippetLinkRange sr in sl.Ranges)
					{
						if (sr.PositionInRange(pos))
						{
							end = true;
							break;
						}
					}
					if (end)
						break;
				}

				if (end)
				{
					cascadeSnippetLinkRangeChange(_snippetLinks.ActiveSnippetLink, _snippetLinks.ActiveRange);

					if (_snippetLinks.EndPoint != null)
						Scintilla.Caret.Goto(_snippetLinks.EndPoint.Start);

					IsActive = false;
					Scintilla.Commands.StopProcessingCommands = true;
					return true;
				}
			}

			return false;
		}

		public bool DoSnippetCheck()
		{
			if (!_isEnabled || _snippetLinks.IsActive || Scintilla.AutoComplete.IsActive || Scintilla.Selection.Length > 0)
				return false;

			int pos = Scintilla.NativeInterface.GetCurrentPos();

			//	In order to even start searching for a snippet keyword the
			//	current position needs to meet these conditions:
			//	Can't be at the very beginning of the document. Why? becuase
			//	then obviously there can't be a preceding keyword then can it?
			//	The preceding character can't be whitespace (same reason) 
			//
			//	I decided I like expanding a template in the middle of a word
			if (pos <= 0 || Scintilla.NativeInterface.GetCharAt(pos - 1).ToString().IndexOfAny(Scintilla.Lexing.WhiteSpaceCharsArr) >= 0)
				return false;

			//	We also don't want a template expand if we're in a Comment or 
			//	String. Be sure and mask out any indicator style that may be applied
			int currentStyle = Scintilla.NativeInterface.GetStyleAt(pos - 1) & 0x1f;
			if (currentStyle == 1 || currentStyle == 2 || currentStyle == 4)
				return false;

			//	Let Scintilla figure out where the beginning of
			//	the word to the left is.
			int newPos = Scintilla.NativeInterface.WordStartPosition(pos, true);

			Range snipRange = Scintilla.GetRange(newPos, pos);
			string keyworkCandidate = snipRange.Text;

			Snippet snip;
			if (!_list.TryGetValue(keyworkCandidate, out snip))
			{
				//	Not a match. Buh-bye
				return false;
			}

			InsertSnippet(snip, newPos);
			Scintilla.Commands.StopProcessingCommands = true;
			return true;
		}

		public void InsertSnippet(string shortcut)
		{
			Snippet snip;
			if (!_list.TryGetValue(shortcut, out snip))
			{
				//	Not a match. Buh-bye
				return;
			}

			InsertSnippet(snip, Math.Min(NativeScintilla.GetCurrentPos(), NativeScintilla.GetAnchor()));
		}

		public void InsertSnippet(Snippet snip)
		{
			InsertSnippet(snip, Math.Min(NativeScintilla.GetCurrentPos(), NativeScintilla.GetAnchor()));
		}

		internal void InsertSnippet(Snippet snip, int startPos)
		{
			NativeScintilla.BeginUndoAction();
			IsActive = false;

			string snippet = snip.RealCode;

			//	First properly indent the template. We do this by
			//	getting the indent string of the current line and
			//	adding it to all newlines
			int indentPoint = 0;
			string line = Scintilla.Lines.Current.Text;
			if(line != string.Empty)
			{				
				while (true)
				{
					char c = line[indentPoint];
					if (c != ' ' && c != '\t')
						break;

					indentPoint++;
				}
			}

			//	Grab the current selected text in case we have a surrounds with scenario.
			string selText = Scintilla.Selection.Text;
			//	Now we clear the selection
			if (selText != string.Empty)
				Scintilla.Selection.Clear();

			if (indentPoint > 0)
			{
				string indent = line.Substring(0, indentPoint);

				//	This is a bit of a tough decision, but I think the best way to handle it
				//	is to assume that the Snippet's Eol Marker matches the Platform DOCUMENT_DEFAULT
				//	but the target Eol Marker should match the Document's.
				snippet = snippet.Replace(Environment.NewLine, Scintilla.EndOfLine.EolString + indent);

				//	Same deal with the selected text if any				
				selText = selText.Replace(Environment.NewLine, Scintilla.EndOfLine.EolString + indent);
			}

			int anchorPos = -1;
			int caretPos = -1;
			int endPos = -1;
			int selPos = -1;
			SortedList<int, int> dropMarkers = new SortedList<int, int>();
			SortedList<int, SnippetLinkRange> indexedRangesToActivate = new SortedList<int, SnippetLinkRange>();
			List<SnippetLinkRange> unindexedRangesToActivate = new List<SnippetLinkRange>();
			Match m = snippetRegex1.Match(snippet);

			while (m.Success)
			{
				//	Did it match a $DropMarker$ token?
				if (m.Groups["dm"].Success)
				{
					//	Yep, was it an indexed or unindexed DropMarker
					if (m.Groups["dmi"].Success)
					{
						//	Indexed, set the indexed drop marker's character offset
						//	if it is specified more than once the last one wins.
						dropMarkers[int.Parse(m.Groups["dmi"].Value)] = m.Groups["dm"].Index;
					}
					else
					{
						//	Unindexed, just tack it on at the end
						dropMarkers[dropMarkers.Count] = m.Groups["dm"].Index;
					}

					//	Take the token out of the string
					snippet = snippet.Remove(m.Groups["dm"].Index, m.Groups["dm"].Length);
				}
				else if (m.Groups["c"].Success)
				{
					//	We matched the $Caret$ Token. Since there can be 
					//	only 1 we set the caretPos. If this is specified
					//	more than once the last one wins
					caretPos = m.Groups["c"].Index;

					//	Take the token out of the string
					snippet = snippet.Remove(m.Groups["c"].Index, m.Groups["c"].Length);
				}
				else if (m.Groups["a"].Success)
				{
					//	We matched the $Anchor$ Token. Since there can be 
					//	only 1 we set the anchorPos. If this is specified
					//	more than once the last one wins
					anchorPos = m.Groups["a"].Index;

					//	Take the token out of the string
					snippet = snippet.Remove(m.Groups["a"].Index, m.Groups["a"].Length);
				}
				else if (m.Groups["e"].Success)
				{
					//	We matched the $End$ Token. Since there can be 
					//	only 1 we set the endPos. If this is specified
					//	more than once the last one wins
					endPos = m.Groups["e"].Index;

					//	Take the token out of the string
					snippet = snippet.Remove(m.Groups["e"].Index, m.Groups["e"].Length);
				}
				else if (m.Groups["s"].Success)
				{
					//	We matched the $Selection$ Token. Simply insert the
					//	selected text at this position
					selPos = m.Groups["s"].Index;

					//	Take the token out of the string
					snippet = snippet.Remove(m.Groups["s"].Index, m.Groups["s"].Length);
					snippet = snippet.Insert(m.Groups["s"].Index, selText);
				}
				else if (m.Groups["l"].Success)
				{
					//	Finally match for Snippet Link Ranges. This is at the bottom of the if/else
					//	because we want the more specific regex groups to match first so that this
					//	generic expression group doesn't create a SnippetLinkRange for say the 
					//	$Caret$ Token.
					Group g = m.Groups["l"];

					int rangeIndex;
					string groupKey;

					if (m.Groups["li"].Success)
					{
						//	We have a subindexed SnippetLinkRange along the lines of $sometoken[1]$
						Group sg = m.Groups["li"];

						//	At this point g.Value = $sometoken[1]$
						//	and sg.Value = [1].
						//	We want the range's Key, which would be sometoken
						groupKey = g.Value.Substring(1, g.Value.Length - sg.Length - 2);

						//	Now we need the range's Index which would be 1 in our fictitional case
						rangeIndex = int.Parse(sg.Value.Substring(1, sg.Value.Length - 2));

						//	Now we need to determine the actual start and end positions of the range.
						//	Keep in mind we'll be stripping out the 2 $ and the subindex string (eg [1])
						int start = startPos + g.Index;
						int end = start + g.Length - sg.Length - 2;

						//	And now we add (or replace) the snippet link range at the index
						//	keep in mind duplicates will stomp all over each other, the last
						//	one wins. Replaced tokens won't get a range
						indexedRangesToActivate[rangeIndex] = new SnippetLinkRange(start, end, Scintilla, groupKey); ;

						//	And remove all the token info including the subindex from the snippet text 
						//	leaving only the key
						snippet = snippet.Remove(g.Index, 1).Remove(g.Index - 2 + g.Length - sg.Length, sg.Length + 1);
					}
					else
					{
						//	We have a regular old SnippetLinkRange along the lines of $sometoken$

						//	We want the range's Key, which would be sometoken
						groupKey = g.Value.Substring(1, g.Value.Length - 2);

						//	Now we need to determine the actual start and end positions of the range.
						//	Keep in mind we'll be stripping out the 2 $
						int start = startPos + g.Index;
						int end = start + g.Length - 2;

						//	Now create the range object
						unindexedRangesToActivate.Add(new SnippetLinkRange(start, end, Scintilla, groupKey));

						//	And remove all the token info from the snippet text 
						//	leaving only the key
						snippet = snippet.Remove(g.Index, 1).Remove(g.Index + g.Length - 2, 1);
					}
				}
				//	Any more matches? Note that I'm rerunning the regexp query
				//	on the snippet string becuase it's contents have been modified
				//	and we need to get the updated index values.
				m = snippetRegex1.Match(snippet);
			}

			//	Replace the snippet Keyword with the snippet text. Or if this
			//	isn't triggered by a shortcut, it will insert at the current
			//	Caret Position
			Scintilla.GetRange(startPos, NativeScintilla.GetCurrentPos()).Text = snippet;

			//	Now that we have the text set we can activate our link ranges
			//	we couldn't do it before becuase they were managed ranges and
			//	would get offset by the text change

			//	Since we are done adding new SnippetLinkRanges we can tack
			//	on the unindexed ranges to the end of the indexed ranges
			SnippetLinkRange[] allLinks = new SnippetLinkRange[indexedRangesToActivate.Count + unindexedRangesToActivate.Count];
			for (int i = 0; i < indexedRangesToActivate.Values.Count; i++)
				allLinks[i] = indexedRangesToActivate[i];

			for (int i = 0; i < unindexedRangesToActivate.Count; i++)
				allLinks[i + indexedRangesToActivate.Count] = unindexedRangesToActivate[i];

			foreach (SnippetLinkRange slr in allLinks)
				addSnippetLink(slr);

			foreach (SnippetLinkRange slr in allLinks)
				slr.Init();

			//	Now we need to activate the Snippet links. However we have a bit
			//	of a styling confilct. If we set the indicator styles before the
			//	SQL Lexer styles the newly added text it won't get styled. So to
			//	make sure we set the Indicator Styles after we put the call on
			//	a timer.
			if (_snippetLinks.Count > 0)
			{
				Timer t = new Timer();
				t.Interval = 10;

				//	Oh how I love anonymous delegates, this is starting to remind
				//	me of JavaScript and SetTimeout...
				t.Tick += new EventHandler(delegate(object sender, EventArgs te)
				{
					t.Dispose();
					IsActive = true;
				});
				t.Start();
			}

			//	Add all the Drop markers in the indexed order. The
			//	order is reversed of course because drop markers work
			//	in a FILO manner
			for (int i = dropMarkers.Count - 1; i >= 0; i--)
				Scintilla.DropMarkers.Drop(startPos + dropMarkers.Values[i]);

			//	Place the caret at either the position of the token or
			//	at the end of the snippet text.
			if (caretPos >= 0)
				Scintilla.Caret.Goto(startPos + caretPos);
			else
				Scintilla.Caret.Goto(startPos + snippet.Length);

			//	Ahoy, way anchor!
			if (anchorPos >= 0)
				Scintilla.Caret.Anchor = startPos + anchorPos;

			//	Do we have an end cursor?
			if (endPos >= 0)
			{
				//	If they have snippet link ranges activated in this snippet
				//	go ahead and set up an EndPoint marker
				if (allLinks.Length > 0)
				{
					SnippetLinkEnd eci = new SnippetLinkEnd(endPos + startPos, Scintilla);
					Scintilla.ManagedRanges.Add(eci);
					_snippetLinks.EndPoint = eci;
				}
				else
				{
					//	Otherwise we treat it like an Anchor command because
					//	the SnippetLink mode isn't activated
					Scintilla.Caret.Goto(endPos + startPos);
				}
			}

			NativeScintilla.EndUndoAction();
		}

		#endregion

		#region Event Handlers
		private void _snippetLinkTimer_Tick(object sender, EventArgs e)
		{
			_snippetLinkTimer.Enabled = false;
			Range sr = Scintilla.Selection.Range;

			if (_snippetLinks.IsActive)
			{
				SnippetLink oldActiveSnippetLink = _snippetLinks.ActiveSnippetLink;
				SnippetLinkRange oldActiveRange = _snippetLinks.ActiveRange;

				if (oldActiveRange != null && (oldActiveRange.IntersectsWith(sr) || oldActiveRange.Equals(sr)))
				{
					Scintilla.BeginInvoke(new MethodInvoker(delegate()
					{
						cascadeSnippetLinkRangeChange(oldActiveSnippetLink, oldActiveRange);

						foreach (SnippetLink sl in _snippetLinks.Values)
							foreach (Range r in sl.Ranges)
							{
								if (sl == _snippetLinks.ActiveSnippetLink)
								{
									r.ClearIndicator(Scintilla.Snippets.InactiveSnippetIndicator);
									r.SetIndicator(Scintilla.Snippets.ActiveSnippetIndicator);
								}
								else
								{
									r.SetIndicator(Scintilla.Snippets.InactiveSnippetIndicator);
									r.ClearIndicator(Scintilla.Snippets.ActiveSnippetIndicator);
								}

							}

						if (_pendingUndo)
						{
							_pendingUndo = false;
							Scintilla.UndoRedo.EndUndoAction();
						}

						Scintilla.NativeInterface.Colourise(0, -1);
					}));
				}
			}
		}

		private void Scintilla_TextInserted(object sender, TextModifiedEventArgs e)
		{
			//	I'm going to have to look into making this a little less "sledge hammer to
			//	the entire documnet"ish
			if (_snippetLinks.IsActive && (e.UndoRedoFlags.IsUndo || e.UndoRedoFlags.IsRedo))
				Scintilla.NativeInterface.Colourise(0, -1);
		}

		private void Scintilla_BeforeTextInsert(object sender, TextModifiedEventArgs e)
		{
			if (_snippetLinks.IsActive && !_pendingUndo && !(e.UndoRedoFlags.IsUndo || e.UndoRedoFlags.IsRedo))
			{
				_pendingUndo = true;
				Scintilla.UndoRedo.BeginUndoAction();
				_snippetLinkTimer.Enabled = true;
			}
		}

		private void Scintilla_BeforeTextDelete(object sender, TextModifiedEventArgs e)
		{
			if (!_isEnabled)
				return;

			if (_snippetLinks.IsActive && !_pendingUndo && !(e.UndoRedoFlags.IsUndo || e.UndoRedoFlags.IsRedo))
			{
				_pendingUndo = true;
				Scintilla.UndoRedo.BeginUndoAction();
				_snippetLinkTimer.Enabled = true;
			}

			ManagedRange undoneSnippetLinkRange = null;
			if (e.UndoRedoFlags.IsUndo && _snippetLinks.IsActive)
			{
				foreach (ManagedRange mr in Scintilla.ManagedRanges)
				{
					if (mr.Start == e.Position && mr.Length == e.Length && mr.Length > 1)
					{
						undoneSnippetLinkRange = mr;

						//	Expanding the range So that it won't get marked for deletion
						mr.End++;
					}
				}
			}

			//	It's possible that the end point may have been deleted. The endpoint
			//	is an ultra persistent marker that cannot be deleted until the Snippet
			//	Link mode is deactivated. Place a new EndPoint at the begining of the
			//	deleted range.
			if (_snippetLinks.IsActive && _snippetLinks.EndPoint != null && _snippetLinks.EndPoint.Scintilla == null)
			{
				SnippetLinkEnd eci = new SnippetLinkEnd(e.Position, Scintilla);
				Scintilla.ManagedRanges.Add(eci);
				_snippetLinks.EndPoint = eci;
			}

			//	Now collapse the Undone range in preparation for the
			//	newly inserted text that will be put in here
			if (undoneSnippetLinkRange != null)
				undoneSnippetLinkRange.End = undoneSnippetLinkRange.Start;

			//	Check to see if all SnippetLink ranges have been deleted.
			//	If this is the case we need to turn Deactivate SnippetLink
			//	mode.

			bool deactivate = true;
			foreach (SnippetLink sl in _snippetLinks.Values)
			{
				if (sl.Ranges.Count > 0)
				{
					foreach (SnippetLinkRange slr in sl.Ranges)
					{
						if (slr.Scintilla != null)
						{
							deactivate = false;
							break;
						}
					}
				}
				if (!deactivate)
					break;
			}

			if (deactivate && IsActive)
				IsActive = false;
		}

		private void Scintilla_SelectionChanged(object sender, EventArgs e)
		{
			Range sr = Scintilla.Selection.Range;

			if (_snippetLinks.IsActive)
			{
				SnippetLink oldActiveSnippetLink = _snippetLinks.ActiveSnippetLink;
				SnippetLinkRange oldActiveRange = _snippetLinks.ActiveRange;

				_snippetLinks.ActiveSnippetLink = null;
				_snippetLinks.ActiveRange = null;

				for (int i = 0; i < _snippetLinks.Count; i++)
				{

					SnippetLink sl = _snippetLinks[i];

					foreach (SnippetLinkRange r in sl.Ranges)
					{
						if (r.IntersectsWith(sr))
						{
							_snippetLinks.ActiveSnippetLink = sl;
							_snippetLinks.ActiveRange = r;
							break;
						}
					}
					if (_snippetLinks.ActiveRange != null)
						break;
				}

				foreach (SnippetLink sl in _snippetLinks.Values)
					foreach (Range r in sl.Ranges)
					{
						if (sl == _snippetLinks.ActiveSnippetLink)
						{
							r.ClearIndicator(Scintilla.Snippets.InactiveSnippetIndicator);
							r.SetIndicator(Scintilla.Snippets.ActiveSnippetIndicator);
						}
						else
						{
							r.SetIndicator(Scintilla.Snippets.InactiveSnippetIndicator);
							r.ClearIndicator(Scintilla.Snippets.ActiveSnippetIndicator);
						}
					}
			}
		}

		#endregion

		#region IsOneKeySelectionEmbedEnabled
		//	Yeah I know this is a bit unwieldly but I can't come up with a better name
		private bool _isOneKeySelectionEmbedEnabled = false;

		public bool IsOneKeySelectionEmbedEnabled
		{
			get
			{
				return _isOneKeySelectionEmbedEnabled;
			}
			set
			{
				_isOneKeySelectionEmbedEnabled = value;
			}
		}
		private bool ShouldSerializeIsOneKeySelectionEmbedEnabled()
		{
			return _isOneKeySelectionEmbedEnabled;
		}

		private void ResetIsOneKeySelectionEmbedEnabled()
		{
			_isOneKeySelectionEmbedEnabled = false;
		} 
		#endregion

		#region Private Methods
		private void cascadeSnippetLinkRangeChange(SnippetLink oldActiveSnippetLink, SnippetLinkRange oldActiveRange)
		{
			Scintilla.ManagedRanges.Sort();

			int offset = 0;

			string newText = oldActiveRange.Text;


			Scintilla.NativeInterface.SetModEventMask(0);
			foreach (ManagedRange mr in Scintilla.ManagedRanges)
			{
				if (offset != 0)
					mr.Change(mr.Start + offset, mr.End + offset);

				SnippetLinkRange slr = mr as SnippetLinkRange;
				if (slr == null || !oldActiveSnippetLink.Ranges.Contains(slr) || slr.Text == newText)
					continue;

				int oldLength = slr.Length;
				slr.Text = newText;
				slr.End += newText.Length - oldLength;
				offset += newText.Length - oldLength;
			}

			Scintilla.NativeInterface.SetModEventMask(Constants.SC_MODEVENTMASKALL);
		}

		private SnippetLinkRange addSnippetLink(SnippetLinkRange range)
		{
			string key = range.Key;
			SnippetLink sl = null;
			for (int i = 0; i < _snippetLinks.Count; i++)
			{
				if (_snippetLinks[i].Key.Equals(key, StringComparison.CurrentCultureIgnoreCase))
				{
					sl = _snippetLinks[i];
					break;
				}
			}
			if (sl == null)
			{
				sl = new SnippetLink(key);
				_snippetLinks.Add(sl);
			}

			sl.Ranges.Add(range);
			range.Parent = sl.Ranges;

			return range;
		}


		#endregion

		#region Public Properties

		#region Active
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsActive
		{
			get
			{
				return _snippetLinks.IsActive;
			}
			internal set
			{
				_snippetLinks.IsActive = value;

				if (value)
				{
					SetIndicators();
					_snippetLinks[0].Ranges[0].Select();
				}
				else
				{
					//	Deactivating Snippet Link mode. First make sure all
					//	the snippet link ranges have their indicators cleared
					foreach (SnippetLink sl in _snippetLinks.Values)
						foreach (Range r in sl.Ranges)
						{
							r.ClearIndicator(Scintilla.Snippets.InactiveSnippetIndicator);
							r.ClearIndicator(Scintilla.Snippets.ActiveSnippetIndicator);
						}

					//	Then clear out the _snippetLinks list cuz we're done with them
					_snippetLinks.Clear();

					if (_snippetLinks.EndPoint != null)
					{
						_snippetLinks.EndPoint.Dispose();
						_snippetLinks.EndPoint = null;
					}
				}
			}
		} 
		#endregion

		#region ActiveSnippetColor
		private Color _activeSnippetColor = Color.Lime;
		public Color ActiveSnippetColor
		{
			get
			{
				return _activeSnippetColor;
			}
			set
			{
				_activeSnippetColor = value;
			}
		}

		private bool ShouldSerializeActiveSnippetColor()
		{
			return _activeSnippetColor != Color.Lime;
		}

		private void ResetActiveSnippetColor()
		{
			_activeSnippetColor = Color.Lime;
		}
		#endregion

		#region ActiveSnippetIndicator
		private int _activeSnippetIndicator = 15;
		public int ActiveSnippetIndicator
		{
			get
			{
				return _activeSnippetIndicator;
			}
			set
			{
				_activeSnippetIndicator = value;
			}
		}

		private bool ShouldSerializeActiveSnippetIndicator()
		{
			return _activeSnippetIndicator != 15;
		}

		private void ResetActiveSnippetIndicator()
		{
			_activeSnippetIndicator = 15;
		} 
		#endregion

		#region ActiveSnippetIndicatorStyle
		private IndicatorStyle _activeSnippetIndicatorStyle = IndicatorStyle.RoundBox;
		public IndicatorStyle ActiveSnippetIndicatorStyle
		{
			get
			{
				return _activeSnippetIndicatorStyle;
			}
			set
			{
				_activeSnippetIndicatorStyle = value;
			}
		}

		private bool ShouldSerializeActiveSnippetIndicatorStyle()
		{
			return _activeSnippetIndicatorStyle != IndicatorStyle.RoundBox;
		}

		private void ResetActiveSnippetIndicatorStyle()
		{
			_activeSnippetIndicatorStyle = IndicatorStyle.RoundBox;
		} 
		#endregion

		#region DefaultDelimeter
		private char _defaultDelimeter = '$';
		public char DefaultDelimeter
		{
			get
			{
				return _defaultDelimeter;
			}
			set
			{
				_defaultDelimeter = value;
			}
		}

		private bool ShouldSerializeDefaultDelimeter()
		{
			return _defaultDelimeter != '$';
		}

		private void ResetDefaultDelimeter()
		{
			_defaultDelimeter = '$';
		} 
		#endregion

		#region IsEnabled
		private bool _isEnabled = true; 
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
		public bool IsEnabled
		{
			get
			{
				return _isEnabled;
			}
			set
			{
				_isEnabled = value;

				if (value)
				{
					Scintilla.TextInserted += new EventHandler<TextModifiedEventArgs>(Scintilla_TextInserted);
					Scintilla.BeforeTextInsert += new EventHandler<TextModifiedEventArgs>(Scintilla_BeforeTextInsert);
					Scintilla.BeforeTextDelete += new EventHandler<TextModifiedEventArgs>(Scintilla_BeforeTextDelete);
					Scintilla.SelectionChanged += new EventHandler(Scintilla_SelectionChanged);
				}
				else
				{
					Scintilla.TextInserted -= new EventHandler<TextModifiedEventArgs>(Scintilla_TextInserted);
					Scintilla.BeforeTextInsert -= new EventHandler<TextModifiedEventArgs>(Scintilla_BeforeTextInsert);
					Scintilla.BeforeTextDelete -= new EventHandler<TextModifiedEventArgs>(Scintilla_BeforeTextDelete);
					Scintilla.SelectionChanged -= new EventHandler(Scintilla_SelectionChanged);
				}
			}
		}

		private bool ShouldSerializeIsEnabled()
		{
			return !_isEnabled;
		}

		private void ResetIsEnabled()
		{
			_isEnabled = true;
		}
		#endregion

		#region InactiveSnippetColor
		private Color _inactiveSnippetColor = Color.Lime;
		public Color InactiveSnippetColor
		{
			get
			{
				return _inactiveSnippetColor;
			}
			set
			{
				_inactiveSnippetColor = value;
			}
		}

		private bool ShouldSerializeInactiveSnippetColor()
		{
			return _inactiveSnippetColor != Color.Lime;
		}

		private void ResetInactiveSnippetColor()
		{
			_inactiveSnippetColor = Color.Lime;
		} 
		#endregion

		#region InactiveSnippetIndicator
		private int _inactiveSnippetIndicator = 16;
		public int InactiveSnippetIndicator
		{
			get
			{
				return _inactiveSnippetIndicator;
			}
			set
			{
				_inactiveSnippetIndicator = value;
			}
		}

		private bool ShouldSerializeInactiveSnippetIndicator()
		{
			return _inactiveSnippetIndicator != 16;
		}

		private void ResetInactiveSnippetIndicator()
		{
			_inactiveSnippetIndicator = 16;
		}
		#endregion

		#region InactiveSnippetIndicatorStyle
		private IndicatorStyle _inactiveSnippetIndicatorStyle = IndicatorStyle.Box;
		public IndicatorStyle InactiveSnippetIndicatorStyle
		{
			get
			{
				return _inactiveSnippetIndicatorStyle;
			}
			set
			{
				_inactiveSnippetIndicatorStyle = value;
			}
		}

		private bool ShouldSerializeInactiveSnippetIndicatorStyle()
		{
			return _inactiveSnippetIndicatorStyle != IndicatorStyle.Box;
		}

		private void ResetInactiveSnippetIndicatorStyle()
		{
			_inactiveSnippetIndicatorStyle = IndicatorStyle.Box;
		}

		#endregion

		#region List
		private SnippetList _list;
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public SnippetList List
		{
			get
			{
				return _list;
			}
			set
			{
				_list = value;
			}
		} 
		#endregion

		#endregion

		public void ShowSnippetList()
		{
			if (_list.Count == 0)
				return;

			if (_snipperChooser == null)
			{
				_snipperChooser = new SnippetChooser();
				_snipperChooser.Scintilla = Scintilla;
				_snipperChooser.SnippetList = _list.ToString();
				_snipperChooser.Scintilla.Controls.Add(_snipperChooser);
			}
			_snipperChooser.SnippetList = _list.ToString();
			_snipperChooser.Show();
		}

		public void ShowSurroundWithList()
		{
			SnippetList sl = new SnippetList(null);
			foreach (Snippet s in _list)
			{
				if (s.IsSurroundsWith)
					sl.Add(s);
			}

			if (sl.Count == 0)
				return;

			if (_snipperChooser == null)
			{
				_snipperChooser = new SnippetChooser();
				_snipperChooser.Scintilla = Scintilla;
				_snipperChooser.SnippetList = _list.ToString();
				_snipperChooser.Scintilla.Controls.Add(_snipperChooser);
			}
			_snipperChooser.SnippetList = sl.ToString();
			_snipperChooser.Show();
		}
	}
}